﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Timers;

namespace ZOVNP.WinView
{
    /// <summary> Делегат установки соединения </summary>
    /// <param name="param"> параметры </param>
    [Description("Делегат установки соединения")]
    public delegate void ConnectionDelegate(Login param);

    /// <summary> Логин к SQL серверу </summary>
    public class Login : IDisposable, INotifyPropertyChanged
    {
        #region Поля

        /// <summary> Служебная строковая константа </summary>
        private const string BROWSE_SERVERS = "<Найти еще...>";
        /// <summary> Событие завершения выполнения соединения с источником данных </summary>
        public event AsyncCompletedEventHandler ConnectCompleted;
        /// <summary> Событие выполнения соединения с источником данных </summary>
        public event SendOrPostCallback DoConnect;
        /// <summary> Событие изменения свойства объекта </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary> Событие необходимости выбора сервера </summary>
        public event EventHandler<DataSourceNeededEventArgs> DataSourceNeeded;
        /// <summary> Контекст синхронизации </summary>
        private SynchronizationContext m_uiContext;
        /// <summary> Имя пользователя </summary>
        protected string m_userId;
        /// <summary> Пароль </summary>
        private string m_password;
        /// <summary> Новый пароль (если требуется смена) </summary>
        private string m_passwordNew;
        /// <summary> Флаг SSPI </summary>
        private bool m_integratedSecurity;
        /// <summary> Список серверов </summary>
        private List<string> m_listDataSource;
        /// <summary> Список БД </summary>
        private List<string> m_listInitialCatalog;
        /// <summary> Текущее имя БД </summary>
        private string m_currentInitialCatalog;
        /// <summary> Текущее имя сервера </summary>
        private string m_currentDataSource;
        /// <summary> Фоновый поток </summary>
        private Thread m_backgroundThread;
        /// <summary> Делегат установления соединения </summary>
        private ConnectionDelegate m_delegate;
        /// <summary> Таймер для уведомлений </summary>
        private System.Timers.Timer m_timer;
        /// <summary> Временный флаг </summary>
        private bool m_ignored;

        #endregion

        /// <summary> Конструктор по-умолчанию </summary>
        public Login()
        {
            m_listDataSource = new List<string>();
            m_listDataSource.Add(BROWSE_SERVERS);
            m_listInitialCatalog = new List<string>();
            m_password = String.Empty;
        }

        #region Свойства

        /// <summary> Получить (установить) имя пользователя </summary>
        /// <value> имя </value>
        public string UserID
        {
            get { return (m_integratedSecurity ? String.Empty : m_userId); }
            set { m_userId = value; }
        }
        /// <summary> Получить (установить) пароль </summary>
        /// <value> пароль </value>
        public string Password
        {
            get { return (m_integratedSecurity ? String.Empty : m_password); }
            set { m_password = value; }
        }
        /// <summary> Получить (установить) новый пароль </summary>
        /// <value> пароль </value>
        public string PasswordNew
        {
            get { return (m_passwordNew); }
            set { m_passwordNew = value; }
        }
        /// <summary> Получить список имен серверов </summary>
        /// <value> список строк </value>
        ///<remarks> Только для bindings на combobox </remarks>
        public List<string> DataSource { get { return (m_listDataSource); } }
        /// <summary> Добавить имя сервера </summary>
        /// <param name="name"> имя сервера </param>
        public void AddDataSource(string name)
        {
            // distinct, not null
            if (!String.IsNullOrEmpty(name) && !m_listDataSource.Contains(name))
                m_listDataSource.Insert(m_listDataSource.Count - 1, name);
        }
        /// <summary> Добавить массив имен серверов </summary>
        /// <param name="collection"> массив имен серверов </param>
        public void AddDataSourceRange(IEnumerable<string> collection)
        {
            if (collection != null)
            {
                foreach (string name in collection)
                    this.AddDataSource(name);
            }
        }
        /// <summary> Добавить имя БД </summary>
        /// <param name="name"> имя БД </param>
        public void AddInitialCatalog(string name)
        {
            // distinct, not null
            if (!String.IsNullOrEmpty(name) && !m_listInitialCatalog.Contains(name))
                m_listInitialCatalog.Add(name);
        }
        /// <summary> Добавить массив имен БД </summary>
        /// <param name="collection"> массив имен БД </param>
        public void AddInitialCatalogRange(IEnumerable<string> collection)
        {
            if (collection != null)
            {
                foreach (string name in collection)
                    this.AddInitialCatalog(name);
            }
        }
        /// <summary> Получить (установить) текущее (выбранное) имя сервера </summary>
        /// <value> строковое имя сервера </value>
        public string CurrentDataSource
        {
            get { return (m_currentDataSource); }
            set
            {
                if (!m_ignored) // потом надо что-нибудь сделать с этим....
                {
                    if (value == BROWSE_SERVERS)
                    {
                        string tmp = GetDataSource();
                        // только если было выбрано
                        if (!String.IsNullOrEmpty(tmp))
                        {
                            m_currentDataSource = tmp;
                        }

                        // если текущее значение из списка, то придет еще одна команда на установку
                        m_ignored = (m_listDataSource.IndexOf(m_currentDataSource) >= 0);
                        // генерируем событие через Post...
                        this.SynchronizationContext.Post(OnPropertyChanged, new PropertyChangedEventArgs("CurrentDataSource"));
                    }
                    else
                        m_currentDataSource = value;
                }
                else
                    m_ignored = false;
            }
        }
        /// <summary> Получить список имен БД </summary>
        /// <value> список строк </value>
        ///<remarks> Только для bindings на combobox </remarks>
        public List<string> InitialCatalog { get { return (m_listInitialCatalog); } }
        /// <summary> Получить (установить) текущее (выбранное) имя БД </summary>
        /// <value> имя БД </value>
        public string CurrentInitialCatalog
        {
            get { return (m_currentInitialCatalog); }
            set { m_currentInitialCatalog = value; }
        }
        /// <summary> Получить (установить) флаг SSPI </summary>
        /// <value> true, если SSPI используется, в противном случае - false </value>
        public bool IntegratedSecurity
        {
            get { return (m_integratedSecurity); }
            set { m_integratedSecurity = value; }
        }
        /// <summary> Получить признак возможности логина </summary>
        /// <value> true, если все поля правильно заполнены, в противном случае - false </value>
        /// <remarks> Binding на кнопку ОК </remarks>
        public bool Enabled
        {
            get { return ((!String.IsNullOrEmpty(m_userId) || m_integratedSecurity) && this.IsValidDataSourceInitialCatalog && !this.IsBusy); }
        }
        /// <summary> Получить признак доступности полей "Пользователь" и "Пароль" </summary>
        /// <value> true, если поля доступны, в противном случае - false </value>
        public bool UserIDPasswordEnabled { get { return (!m_integratedSecurity && this.IsNotBusy); } }
        /// <summary> Получить признак видимости (доступности) полей "Сервер" и "База данных" </summary>
        /// <value> true, если поля видимы (доступны), в противном случае - false </value>
        /// <remarks> Поля видимы (доступны) если в них не введены значения </remarks>
        public bool IsValidDataSourceInitialCatalog { get { return (!String.IsNullOrEmpty(m_currentInitialCatalog) && !String.IsNullOrEmpty(m_currentDataSource)); } }
        /// <summary> Получить признак выполнения асинхронной операции </summary>
        /// <value> true, если операция выполняется, в противном случае - false </value>
        public bool IsBusy { get { return ((m_backgroundThread != null) && m_backgroundThread.IsAlive); } }
        /// <summary> Получить признак не выполнения асинхронной операции </summary>
        /// <value> true, если операция не выполняется, в противном случае - false </value>
        public bool IsNotBusy { get { return (!this.IsBusy); } }
        /// <summary> Установить делегат для соединения с источником данных </summary>
        /// <value> делегат </value>
        public ConnectionDelegate ConnectionDelegate { set { m_delegate = value; } }
        /// <summary> Получить (установить) контекст синхронизации </summary>
        /// <value> контекст синхронизации </value>
        public SynchronizationContext SynchronizationContext
        {
            get
            {
                // если не устанавливали, то его реально и не будет
                if (m_uiContext == null)
                    m_uiContext = new SynchronizationContext();
                return (m_uiContext);
            }
            set { m_uiContext = value; }
        }

        #endregion

        /// <summary> Очистка ресурсов </summary>
        /// <param name="disposing"> флаг очистки </param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // гасим поток
                this.Cancel();
            }
        }
        /// <summary> Очистка ресурсов </summary>
        public void Dispose() { Dispose(true); }
        /// <summary> Выполнить соединение асинхронно </summary>
        public void ConnectAsync()
        {
            if (this.IsNotBusy)
            {
                this.Cancel();
                StartTimer();
                m_backgroundThread = new Thread(OnDoWork);
                m_backgroundThread.IsBackground = true;
                m_backgroundThread.Start(this);
                OnPropertyChanged(new PropertyChangedEventArgs("IsNotBusy"));
            }
        }
        /// <summary> Отменить выполнение соединения </summary>
        public void Cancel()
        {
            StopTimer();
            if (this.IsBusy)
            {
                m_backgroundThread.Abort();
                m_backgroundThread = null;
            }
        }
        /// <summary> Выполнить соединение </summary>
        /// <param name="param"> параметры </param>
        /// <remarks> Вызывается не в UI потоке </remarks>
        private void OnDoWork(object param)
        {
            AsyncCompletedEventArgs e = null;
            try
            {
                m_delegate(param as Login);
                e = new AsyncCompletedEventArgs(null, false, null); // OK
            }
            catch (Exception exception)
            {
                if (exception is ThreadAbortException)
                    e = new AsyncCompletedEventArgs(null, true, null); // Cancel
                else
                    e = new AsyncCompletedEventArgs(exception, false, null); // Error
            }
            finally
            {
                this.SynchronizationContext.Post(OnConnectCompleted, e);
            }
        }
        /// <summary> Генерация события завершения выполнения соединения с источником данных </summary>
        /// <param name="e"> параметры события </param>
        private void OnConnectCompleted(object e)
        {
            StopTimer();
            // всегда сбрасываем
            this.PasswordNew = null;
            if (ConnectCompleted != null)
            {
                AsyncCompletedEventArgs args = e as AsyncCompletedEventArgs;
                if ((args != null) && !args.Cancelled && (args.Error == null))
                {
                    // добавляем в список
                    this.AddDataSource(this.CurrentDataSource);
                    this.AddInitialCatalog(this.CurrentInitialCatalog);
                    OnConnectSuccessCompleted();
                }

                ConnectCompleted(this, args);

                OnPropertyChanged(new PropertyChangedEventArgs("IsNotBusy"));
            }
        }
        /// <summary> Обработчик события успешного соединения с источником данных </summary>
        protected virtual void OnConnectSuccessCompleted() { }
        /// <summary> Обработчик события таймера </summary>
        /// <param name="sender"> источник события </param>
        /// <param name="e"> параметры события </param>
        private void OnTimer(object sender, ElapsedEventArgs e)
        {
            if (DoConnect != null)
                // выполняем вызов в указанном потоке
                this.SynchronizationContext.Post(DoConnect, e);
        }
        /// <summary> Запуск таймера </summary>
        private void StartTimer()
        {
            if (m_timer == null)
            {
                m_timer = new System.Timers.Timer(50);
                m_timer.Elapsed += OnTimer;
                //m_timer.Interval = 20;
            }
            if (!m_timer.Enabled)
                m_timer.Start();
        }
        /// <summary> Остановка таймера </summary>
        private void StopTimer()
        {
            if (m_timer != null)
            {
                m_timer.Stop();
                m_timer.Dispose();
                m_timer = null;
            }
        }
        /// <summary> Генерация события изменения свойства </summary>
        /// <param name="e"> параметры события </param>
        protected void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }
        /// <summary> Генерация события изменения свойства </summary>
        /// <param name="e"> параметры события </param>
        /// <remarks> Делегат типа SendOrPostCallback </remarks>
        private void OnPropertyChanged(object e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e as PropertyChangedEventArgs);
        }
        /// <summary> Генерация события необходимости выбора сервера </summary>
        /// <returns> стороковое имя сервера </returns>
        protected string GetDataSource()
        {
            // если нет обработчика, то не работаем
            if (DataSourceNeeded != null)
            {
                DataSourceNeededEventArgs e = new DataSourceNeededEventArgs(new SQLServerInfo.DataSourceEnumerator());
                DataSourceNeeded(this, e);
                return (e.DataSource);
            }
            return (null);
        }
    }
}
